from typing import Callable, List
from threading import Thread, Lock
from datetime import datetime, date, time, timedelta
from time import sleep
from collections import deque

from .constant import TaskType
from .setting import SETTINGS, SETTING_FILENAME
from .utility import save_json


class Task:
    """
    任务
    """

    def __init__(
            self,
            type: TaskType = TaskType.pz,       # 任务类型：盘中 or 盘后
            process_task: Callable = None,      # 任务执行的回调函数
            display_task: Callable = None,      # 显示任务执行结果的回调函数
            interval: int = 0,                  # 对于盘中任务，此参数为时间间隔（分钟）
            task_id: int = -1                   # 对于盘后任务，此参数为任务号，1~6
    ):
        """初始化"""
        self.type: TaskType = type
        self.process_task: Callable = process_task
        self.display_task: Callable = display_task
        self.interval: int = interval
        self.task_id: int = task_id

        # 任务上次执行的日期和时间
        self.last_date: date = None
        self.last_time: time = None


class MessQueue:
    """
    消息队列
    """

    def __init__(self, size: int = 100):
        """初始化"""
        # 最多保存的消息数
        self._size: int = size
        # 消息队列
        self._queue: deque = deque()
        # 互斥锁
        self.lock = Lock()  # 创建一个锁对象

    def put(self, mess: str) -> None:
        """向队列中追加一条消息"""
        self.lock.acquire()             # 申请获取锁
        # 如果消息数量已达上限，从头部删除一个消息
        if len(self._queue) >= self._size:
            self._queue.popleft()
        # 将新消息追加到尾部
        self._queue.append(mess)
        self.lock.release()             # 释放锁

    def get_all(self) -> List:
        """取得所有消息，以列表的形式返回"""
        li_mess = []
        self.lock.acquire()             # 申请获取锁
        for i in range(len(self._queue)):
            li_mess.append(self._queue[i])
        self.lock.release()             # 释放锁
        return li_mess

class TaskEngine:
    """
    任务引擎
    """

    def __init__(self, interval: int = 29):
        """初始化"""
        # 检查是否有任务需要执行的时间间隔
        self._interval: int = interval
        # 引擎的活动标志
        self._active: bool = False
        # 任务列表
        self._tasks: List = []
        # 守护线程
        self._thread: Thread = Thread(target=self._run)
        # 消息列表
        self._queue: MessQueue = MessQueue()

        # 取开盘时间
        self.timeOpen1: time = datetime.strptime(SETTINGS["RW.timeOpen1"], "%H:%M").time()
        self.timeClose1: time = datetime.strptime(SETTINGS["RW.timeClose1"], "%H:%M").time()
        self.timeOpen2: time = datetime.strptime(SETTINGS["RW.timeOpen2"], "%H:%M").time()
        self.timeClose2: time = datetime.strptime(SETTINGS["RW.timeClose2"], "%H:%M").time()

        # 生成所有盘后任务列表
        self.gen_ph_list()

    def gen_ph_list(self):
        """
        生成所有盘后任务列表
        对所有可能的盘后任务，生成一个列表，方便通过任务号存取。
        每一项包括定时的时间以及上次执行的日期。
        注：生成的列表只在程序启动（创建任务）时使用，程序执行的过程中就不再使用。
        """
        # 创建一个空列表
        self.ph_list = []
        self.ph_list.append((0, 0))     # 增加一个无用的元素，占用0号位置，保证真正的任务编号从1~6

        for i in range(1, 7):
            # 生成配置项的名称
            timestr = f"RW.timePH_{i}"
            datestr = f"RW.datePH_{i}"
            # 取配置项值，组合成元组，加入到列表
            try:
                self.ph_list.append((datetime.strptime(SETTINGS[timestr], "%H:%M").time(),
                                     datetime.strptime(SETTINGS[datestr], "%Y-%m-%d").date()))
            except:
                self.ph_list.append((datetime.strptime("15:10", "%H:%M").time(),
                                     datetime.strptime("2000-01-01", "%Y-%m-%d").date()))
            # print(self.ph_list[i])

    def _run(self) -> None:
        """
        守护线程
        """
        counter = self._interval - 1
        while self._active:
            # 计时
            sleep(1)
            counter = counter + 1
            if counter < self._interval:
                continue
            counter = 0
            # 注：每秒循环一次，保证程序退出时能快速响应。检查任务的间隔仍然是self._interval秒。

            # 时间到，开始处理
            for task in self._tasks:
                type = task.type
                if type == TaskType.pz:
                    # 如果是盘中任务
                    self.process_pz_task(task)
                else:
                    # 如果是盘后任务
                    self.process_ph_task(task)

    def process_pz_task(self, task: Task):
        """盘中任务处理"""
        now = datetime.now().time()

        # 如果不是开盘时间，不执行盘中任务
        if now < self.timeOpen1 or (now > self.timeClose1 and now < self.timeOpen2) or now > self.timeClose2:
            return

        # 当前的日期时间
        now_dt = datetime.now()
        # 计算向前task.interval分钟的时间
        last_dt = now_dt - timedelta(minutes=task.interval)
        last_ti = last_dt.time()
        if last_ti > task.last_time:
            # 如果时间已到
            # 执行任务
            mess = task.process_task()
            now_str = now.strftime('%H:%M: ') + mess
            task.display_task(now_str)
            # 将执行结果追加到消息队列
            self._queue.put(now_str)

            # 任务执行完后的处理
            # 注：只需要改时间
            task.last_time = now

    def process_ph_task(self, task: Task):
        """盘后任务处理"""
        # 当前的日期时间
        today = datetime.now().date()
        now = datetime.now().time()

        if task.last_date < today and task.last_time <= now:
            # 如果今天还没有执行，且时间已到
            # 执行任务
            mess = task.process_task()
            now_str = now.strftime('%H:%M: ') + mess
            task.display_task(now_str)
            # 将执行结果追加到消息队列
            self._queue.put(now_str)

            # 任务执行完后的处理
            # 注：只需要改日期
            task.last_date = today
            # 修改后的日期还要保存到配置文件
            task_id = task.task_id
            datestr = f"RW.datePH_{task_id}"
            SETTINGS[datestr] = today.strftime('%Y-%m-%d')
            save_json(SETTING_FILENAME, SETTINGS)

    def start(self) -> None:
        """
        启动任务引擎
        """
        self._active = True
        self._thread.start()
        self._queue.put("任务引擎启动。")

    def stop(self) -> None:
        """
        停止任务引擎
        """
        self._active = False
        self._thread.join()

    def put(self, task: Task) -> None:
        """
        将一个任务加入到列表
        """
        # 当前的日期时间
        today = datetime.now().date()
        now = datetime.now().time()

        # 对任务的合法性进行检查，如果非法，则不加入到任务列表
        # 注：本程序暂未考虑日期的变化，即不支持7*24执行
        type = task.type
        if type == TaskType.pz:
            # 如果是盘中任务
            # 任务时间间隔不能小于等于0
            if task.interval <= 0:
                return

            # 当前时间不能是收盘以后
            # 如果还在盘中，设置任务的task.last_time
            if now < self.timeClose1:
                task.last_time = self.timeOpen1
            elif now < self.timeClose2:
                task.last_time = self.timeOpen2
            else:
                return
        else:
            # 如果是盘后任务
            # 任务号不能小于等于0
            task_id = task.task_id
            if task_id <= 0:
                return

            # 今天已经执行过的盘后任务不再加入列表
            last_date = self.ph_list[task_id][1]
            if last_date >= today:
                # 今天已经执行过
                return

            # 设置任务的task.last_date和task.last_time
            task.last_date = last_date
            task.last_time = self.ph_list[task_id][0]

        # 将任务加入到列表
        self._tasks.append(task)

    def get_all_mess(self) -> List:
        """取得消息队列中的所有消息，以列表的形式返回"""
        return self._queue.get_all()